﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using NCommet.Core.Agents;
using NCommet.Core.Configuration;
using NCommet.Core.Services;
using NCommet.Core.Exceptions;
using log4net;

namespace NCommet.Core
{
    /// <summary>
    /// Item defines a node in the hierarchy.
    /// </summary>
    public class Item
    {
        private static ILog mylog;

        private static ILog MyLog { get { return mylog ?? (mylog = LogManager.GetLogger("NCommet.Core")); } }

        #region Fields and Properties

        private int id;

        /// <summary>
        /// The id of the item, provided by the persister.
        /// </summary>
        public virtual int Id
        {
            get { return id; }
        }

        /// <summary>
        /// The name of the item, a user defined string to identify this item.
        /// </summary>
        protected string name;

        /// <summary>
        /// The name of the item, a user defined string to identify this item.
        /// </summary>
        public virtual string Name
        {
            get { return name; }
            set { name = value; }
        }

        private Item parent;

        /// <summary>
        /// The parent item of this item.
        /// </summary>
        public virtual Item Parent
        {
            get { return parent; }
            internal set
            {
                if (value == this)
                    throw new InvalidOperationException("An item cannot be its own parent");
                Item oldParent = this.parent;
                this.parent = value;
                EventSink.Instance.FireItemChangedParent(new ItemChangedParentArgs(this, oldParent));
            }
        }

        /// <summary>
        /// This is accessed directly from DAO.
        /// </summary>
        private IList<Item> _internalChildren = new List<Item>();

        /// <summary>
        /// Provides direct access to the list of children used by the DAO.
        /// </summary>
        internal virtual IList<Item> internalChildren { get { return _internalChildren; } }

        /// <summary>
        /// Provides access to this item's children.
        /// </summary>
        /// <seealso cref="Children"/>
        protected ChildrenCollection children;

        /// <summary>
        /// Provides access to the children of this item.
        /// </summary>
        public virtual ChildrenCollection Children
        {
            get
            {
                if (children == null)
                    children = new ChildrenCollection(this);
                return children;
            }
        }

        /// <summary>
        /// The date and time when the item was created.
        /// </summary>
        protected DateTime createdAt;

        /// <summary>
        /// The date and time when the item was created.
        /// </summary>
        public virtual DateTime CreatedAt
        {
            get { return createdAt; }
            set { createdAt = value; }
        }

        /// <summary>
        /// This list is accessed by DAO.
        /// </summary>
        private IList<RelItem> _internalRelatives = new List<RelItem>();

        /// <summary>
        /// Provides access to the list of RelItems that the DAO uses.
        /// </summary>
        internal virtual IList<RelItem> internalRelatives { get { return _internalRelatives; } }

        /// <summary>
        /// This RelItem list is used by the user.
        /// </summary>
        protected RelItemCollection relatives;

        /// <summary>
        /// Provides access to the relatives of this item. Each item may
        /// be related to any number of items using a textual representation
        /// to define each type of relationship.
        /// </summary>
        public virtual RelItemCollection Relatives
        {
            get
            {
                if (relatives == null)
                    relatives = new RelItemCollection(this);
                return relatives;
            }
        }

        /// <summary>
        /// The tags this item is tagged with.
        /// </summary>
        protected IList<string> tags = new List<string>();

        /// <summary>
        /// The tags this item is tagged with.
        /// </summary>
        public virtual IList<string> Tags { get { return tags; } }

        /// <summary>
        /// The sort order of this item inside its parent's children collection.
        /// </summary>
        protected int order;

        /// <summary>
        /// The sort order of this item inside its parent's children collection.
        /// </summary>
        internal virtual int Order { get { return order; } set { order = value; } }

        /// <summary>
        /// The type of this item. <seealso cref="ItemType"/>
        /// </summary>
        protected string itemType;

        /// <summary>
        /// The type of this item.
        /// </summary>
        public virtual string ItemType
        {
            get { return itemType; }
        }

        private ContentBase content;

        private void AssertCanContain(ContentBase content)
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            ICollection<string> errors;
            if (ttvalidator != null && !ttvalidator.CanContain(this, content, out errors))
                throw new ValidationException(errors);
        }

        /// <summary>
        /// The content of this item.
        /// </summary>
        public virtual ContentBase Content
        {
            get { return content; }
            set
            {
                if (content != value)
                {
                    AssertAuth(AccessLevel.Edit);

                    // Check that it is a valid content for this item.
                    AssertCanContain(value);

                    ContentBase oldContent = content;
                    content = null;

                    if (oldContent != null)
                    {
                        oldContent.Container = null;
                        IPersister persister = NCommetContainer.Instance.Persister;
                        if (!persister.IsTransient(oldContent))
                            persister.Delete(oldContent);
                    }

                    content = value;
                    if (content != null)
                        content.Container = this;
                }
            }
        }

        #endregion

        /// <summary>
        /// Checks for item equality. If both items are persisted then equality is
        /// checked on their ids.
        /// </summary>
        /// <param name="obj">The object that is checked for equality against this item.</param>
        /// <returns>True, if the items are equal.</returns>
        public override bool Equals(object obj)
        {
            return obj is Item && this == (Item)obj;
        }

        /// <summary>
        /// Returns the hash code of this item. If the item is persisted, its id is returned.
        /// </summary>
        /// <returns>The hashcode of the item.</returns>
        public override int GetHashCode()
        {
            if (this.id > 0)
                return this.id;
            else
                return base.GetHashCode();
        }

        /// <summary>
        /// Checks for equality of two items.
        /// </summary>
        /// <param name="x">The first item.</param>
        /// <param name="y">The second item.</param>
        /// <returns>True, if the items are equal.</returns>
        public static bool operator ==(Item x, Item y)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(x, y))
            {
                return true;
            }

            // If one is null, but not both, return false.
            if (((object)x == null) || ((object)y == null))
            {
                return false;
            }

            // different references
            return x.Id == y.Id && x.Id > 0;
        }

        /// <summary>
        /// Checks for inequality for two items.
        /// </summary>
        /// <param name="x">The first item.</param>
        /// <param name="y">The second item.</param>
        /// <returns>True, if the items are not equal.</returns>
        public static bool operator !=(Item x, Item y)
        {
            return !(x == y);
        }

        #region Factory methods

        protected Item() { }

        /// <summary>
        /// Creates a new item with the given name and item type, optionally persisting the item.
        /// </summary>
        /// <param name="Name">The name of the new item.</param>
        /// <param name="ItemType">The item type of the new item.</param>
        /// <param name="Persist">If true, the item will be persisted at the time of the creation.</param>
        /// <returns>A new item.</returns>
        /// <exception cref="ArgumentException">
        /// When null or empty <paramref name="ItemType"/> is specified.
        /// </exception>
        public static Item Create(string Name, string ItemType, bool Persist)
        {
            if (string.IsNullOrEmpty(ItemType))
            {
                throw new ArgumentException("ItemType cannot be null or empty, because it is immutable.", "ItemType");
            }
            Item result = new Item();
            result.createdAt = DateTime.Now;
            result.name = Name;
            result.itemType = ItemType;
            if (Persist)
                result.Save();
            EventSink.Instance.FireItemCreated(new ItemEventArgs(result));
            return result;
        }

        /// <summary>
        /// Creates a new transient item of the designated name and item type.
        /// </summary>
        /// <param name="Name">The name of the new item.</param>
        /// <param name="ItemType">The type of the new item.</param>
        /// <returns>A new transient item.</returns>
        public static Item Create(string Name, string ItemType)
        {
            return Create(Name, ItemType, false);
        }

        /// <summary>
        /// Creates a new transient item of the designated item type and empty name.
        /// </summary>
        /// <param name="ItemType">The type of the new item.</param>
        /// <returns>A new transient item</returns>
        public static Item Create(string ItemType)
        {
            return Create(string.Empty, ItemType, false);
        }

        /// <summary>
        /// Gets an item from the persister by its id. Authorization check is performed on View access level.
        /// </summary>
        /// <param name="ItemId">The id of the item.</param>
        /// <returns>The persisted item.</returns>
        /// <exception cref="AuthException">
        /// If the item exists, an authorization manager exists and the logged-in user does not have 
        /// <see cref="NCommet.Core.Agents.AccessLevel.View"/> acccess on this item.
        /// </exception>
        public static Item Get(int ItemId)
        {
            return Item.Get(ItemId, false);
        }

        /// <summary>
        /// Gets an item from the persister by its id. Unless OverrideSecurity is true,
        /// authorization check is performed on View Access level.
        /// </summary>
        /// <param name="ItemId">The id of the item.</param>
        /// <param name="OverrideSecurity">If true, an authorization check on View Access level is performed.</param>
        /// <returns>The persisted item.</returns>
        /// <exception cref="AuthException">
        /// If the item exists, OverrideSecurity is false, an authorization manager exists and the logged-in user does not have 
        /// <see cref="NCommet.Core.Agents.AccessLevel.View"/> access on this item.
        /// </exception>
        public static Item Get(int ItemId, bool OverrideSecurity)
        {
            IPersister persister = NCommetContainer.Instance.Persister;
            Item result = persister.Get<Item>(ItemId);
            if (result != null && !OverrideSecurity)
            {
                result.AssertAuth(AccessLevel.View);
            }
            return result;
        }

        #endregion

        /// <summary>
        /// Returns true if this item is transient (i.e. not persisted).
        /// </summary>
        private bool IsTransient
        {
            get
            {
                return NCommetContainer.Instance.Persister.IsTransient(this);
            }
        }

        private void AssertCanSetRoot()
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            ICollection<string> errors;
            if (ttvalidator != null && !ttvalidator.CanSetRoot(this, out errors))
                throw new ValidationException(errors);
        }

        internal virtual void AssertAuth(AccessLevel accessLevel)
        {
            IAuthorizationManager iauth = NCommetContainer.Instance.AuthorizationManager;
            if (iauth != null && !iauth.HasAccess(this, accessLevel))
                throw new AuthException(accessLevel, "AssertAuth failed. ItemId=" + this.Id + ", AccessLevel=" + accessLevel, null);
        }

        #region Persistency Methods

        /// <summary>
        /// Saves the item using the persister.
        /// </summary>
        /// <exception cref="AuthException">
        /// If an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.Edit"/> access on this item.
        /// </exception>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists and the item cannot be set as root.
        /// </exception>
        public virtual void Save()
        {
            AssertAuth(AccessLevel.Edit);

            //Validate the item if it is a root item
            if (this.Parent == null && this.IsTransient)
                AssertCanSetRoot();

            MyLog.DebugFormat("Saving Item ID={0}, Name={1}, ItemType={2}", this.id, this.name, this.itemType);
            if (NCommetContainer.Instance.DaoEventsSink == null)
                EventSink.Instance.FireItemSaving(new ItemEventArgs(this));
            IPersister persister = NCommetContainer.Instance.Persister;
            persister.Save(this);
            if (NCommetContainer.Instance.DaoEventsSink == null)
                EventSink.Instance.FireItemSaved(new ItemEventArgs(this));
        }

        internal virtual void PerformDelete()
        {
            IPersister persister = NCommetContainer.Instance.Persister;
            persister.Delete(this);
            if (NCommetContainer.Instance.DaoEventsSink == null)
                EventSink.Instance.FireItemDeleted(new ItemEventArgs(this));
        }

        /// <summary>
        /// Deletes the item using the persister.
        /// </summary>
        /// <exception cref="AuthException">
        /// If an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.Delete"/> access on this item.
        /// </exception>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists that prevents the item from being removed from its parent.
        /// </exception>        
        public virtual void Delete()
        {
            AssertAuth(AccessLevel.Delete);

            if (Parent != null)
                Parent.Children.Remove(this);
            else
            {
                ItemCancellableEventArgs e = new ItemCancellableEventArgs(this);
                EventSink.Instance.FireItemDeleting(e);
                if (e.Cancel)
                    return;
                PerformDelete();
            }
        }

        #endregion

        #region Static Helpers
        /// <summary>
        /// Converts a list of items to a list of their ids.
        /// </summary>
        /// <param name="items">The list of items.</param>
        /// <returns>A list of the item ids.</returns>
        public static IList<int> ToIntList(IList<Item> items)
        {
            List<int> result = new List<int>(items.Count);
            foreach (Item item in items)
                result.Add(item.Id);
            return result;
        }

        /// <summary>
        /// Converts a list of items to a list of their contents.
        /// </summary>
        /// <typeparam name="T">The type of the content.</typeparam>
        /// <param name="items">The list of items.</param>
        /// <returns>A list of the items contents.</returns>
        public static List<T> ToContentList<T>(List<Item> items) where T : ContentBase
        {
            return items.ConvertAll<T>(delegate(Item it)
            {
                return (T)it.Content;
            });
        }

        /// <summary>
        /// Converts a list of contents to a list of their corresponding items.
        /// </summary>        
        /// <param name="contents">The list of contents.</param>
        /// <returns>A list of the content corresponding items.</returns>
        public static List<Item> ToItemList<T>(List<T> contents) where T : ContentBase
        {
            return contents.ConvertAll<Item>(delegate(T cb)
            {
                return cb.Container;
            });
        }

        #endregion

        #region Item Operations

        /// <summary>
        /// Detaches this item from its parent, making it a root item in the hierarchy.
        /// </summary>
        public virtual void Detach()
        {
            ChangeParent(null);
        }

        /// <summary>
        /// Changes the parent of this item.
        /// </summary>
        /// <param name="NewParent">The new parent.</param>
        /// <exception cref="CoreException">
        /// <ul>
        /// <li>If <paramref name="NewParent"/> is the item itself.</li>
        /// <li>If the item is an ancestor of <paramref name="NewParent"/>.</li>
        /// <li>If the item is placed under one of its descendants.</li>
        /// </ul>
        /// </exception>
        /// <exception cref="AuthException">
        /// <ul>
        /// <li>If the item has a parent, an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.Detach"/> access on this item.</li>
        /// <li>If <paramref name="NewParent"/> is not null, an authorization manager exists and the logged-in user does not have <see cref="NCommet.Core.Agents.AccessLevel.AddChild"/> access on <paramref name="NewParent"/>.</li>        
        /// </ul>
        /// </exception>        
        /// <exception cref="ValidationException">
        /// <ul>        
        /// <li>If a typed tree validator exists that prevents removing the item from its parent.</li>
        /// <li>If a typed tree validator exists that prevents the item from having <paramref name="NewParent"/> as its parent.</li>        
        /// </ul>
        /// </exception>        
        public virtual void ChangeParent(Item NewParent)
        {
            if (NewParent == this)
                throw new Exceptions.CoreException("An item cannot be its own parent");

            if (this.parent == NewParent)
                return; // The same parent man...

            if (this.parent != null)
                AssertAuth(AccessLevel.Detach);

            if (NewParent != null)
            {
                NewParent.AssertAuth(AccessLevel.AddChild);
                if (HierarchyManager.HasAncestor(this, NewParent))
                    throw new CoreException("An item cannot be placed under one of its descendants");
            }

            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            ICollection<string> errors;
            if (ttvalidator != null && !ttvalidator.CanAttach(NewParent, this, out errors))
                throw new ValidationException(errors);

            ItemChangingParentEventArgs e = new ItemChangingParentEventArgs(this, NewParent);
            EventSink.Instance.FireItemChangingParent(e);
            if (e.Cancel)
                return;

            NCommetContainer.Instance.Persister.BeginTransaction();
            try
            {

                Item oldParent = this.parent;
                if (oldParent != null)
                {
                    oldParent.Children.Remove(this, false);
                    oldParent.Save();
                }

                this.Parent = NewParent;
                if (NewParent != null)
                {
                    NewParent.Children.Add(this);
                    NewParent.Save();
                }
                else
                    this.Save();

                NCommetContainer.Instance.Persister.CommitTransaction();
            }
            catch (Exception ex)
            {
                NCommetContainer.Instance.Persister.RollbackTransaction();
            }
        }

        /// <summary>
        /// Decreases by one the item's order in its parent children collection.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// <ul>        
        /// <li>If the parent of the item is null.</li>
        /// <li>If the item's order is zero.</li>        
        /// </ul>
        /// </exception>
        public virtual void MoveUp()
        {
            if (parent == null)
                throw new ArgumentException("Cant move without parent");
            if (this.order <= 0)
                throw new ArgumentException("cant move up when its on top");
            parent.Children.MoveAt(this.order, this.order - 1);
            parent.Save();
        }

        /// <summary>
        /// Increases by one the item's order in its parent children collection.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// <ul>        
        /// <li>If the parent of the item is null.</li>
        /// <li>If the item's order is zero.</li>        
        /// </ul>
        /// </exception>
        public virtual void MoveDown()
        {
            if (parent == null)
                throw new ArgumentException("Cant move without parent");
            if (this.order >= this.parent.Children.Count - 1)
                throw new ArgumentException("cant move down when its on bottom");
            parent.Children.MoveAt(this.order, this.order + 1);
            parent.Save();
        }

        /// <summary>
        /// Moves this item to the position of the given item in its parent children collection.
        /// </summary>
        /// <param name="newPosition">The item with the position we want to place this item.</param>
        /// <exception cref="ArgumentException">
        /// <ul>        
        /// <li>If the parent of the item is null.</li>
        /// <li>If the item's order is zero.</li>        
        /// </ul>
        /// </exception>
        public virtual void MoveAt(Item newPosition)
        {
            if (newPosition == null)
                throw new ArgumentException("Parameter can't be null", "newPosition");
            if (this.Parent != newPosition.Parent)
                throw new ArgumentException("Parameter is not my sibling", "newPosition");
            parent.Children.MoveAt(this.order, newPosition.order);
            parent.Save();
        }

        /// <summary>
        /// Using the persister, returns the relItems where this item paricipates as object.
        /// </summary>
        /// <returns>A list of <see cref="NCommet.Core.RelItem">RelItem</see>.</returns>
        public virtual IList<RelItem> GetReverseRelItems()
        {
            IPersister persister = NCommetContainer.Instance.Persister;
            return persister.GetReverseRelItems(this);
        }

        /// <summary>
        /// Returns the height of this item in the hierarchy. A parentless node
        /// (a root node) has zero height, its children's height is one, etc.
        /// </summary>
        /// <returns>The height of the item in the hierarchy.</returns>
        public virtual int GetLevel()
        {
            int result = 0;
            for (Item i = this.parent; i != null; i = i.parent)
            {
                result++;
            }
            return result;
        }

        /// <summary>
        /// Filters children based on their ItemType using the persister.
        /// </summary>
        /// <param name="ItemType">The type of the item.</param>
        /// <returns>A list of <see cref="NCommet.Core.Item">Items</see>.</returns>
        public virtual IList<Item> FilterChildren(string ItemType)
        {
            IPersister persister = NCommetContainer.Instance.Persister;
            return persister.FilterChildren(this, ItemType);
        }

        /// <summary>
        /// Searches the children of this item to find an item of the specified item type.
        /// </summary>
        /// <param name="ItemType">The type of the item.</param>
        /// <returns>True, if an item with the specified item type exists in the children collection.</returns>
        public virtual bool ContainsChildOfType(string ItemType)
        {
            foreach (Item it in this.Children)
                if (it.itemType == ItemType)
                    return true;
            return false;
        }

        /// <summary>
        /// Filters children of this item using the persister, keeping items whose item type is in the ItemTypes parameter.
        /// </summary>
        /// <param name="ItemTypes">The item types.</param>
        /// <returns>A list of <see cref="NCommet.Core.Item">Items</see>.</returns>
        public virtual IList<Item> FilterChildren(IEnumerable<string> ItemTypes)
        {
            IPersister persister = NCommetContainer.Instance.Persister;
            return persister.FilterChildren(this, ItemTypes);
        }

        /// <summary>
        /// Filters the relItems of this item (where it participates as subject) based on the relation type
        /// and the item type of the relative items.
        /// </summary>
        /// <param name="RelationType">The relation type filter.</param>
        /// <param name="RelativeItemType">The relative item filter.</param>
        /// <returns>A list of <see cref="NCommet.Core.RelItem">RelItem</see>.</returns>
        /// <remarks>
        /// <ul>
        /// <li>If a persister exists, it is used to filter the relItems.</li>
        /// <li>If either of <paramref name="RelationType"/> and <paramref name="RelativeItemType"/> is null, they are ignored from the filtering process
        /// i.e. if <paramref name="RelationType"/> is null then relItems with any relation type are returned.</li>
        /// </ul>
        /// </remarks>
        public virtual List<RelItem> FilterRelItems(string RelationType, string RelativeItemType)
        {
            if (NCommetContainer.Instance.Persister != null)
                return new List<RelItem>(NCommetContainer.Instance.Persister.FilterRelItems(this, RelationType, RelativeItemType));
            else
            {
                List<RelItem> lst = new List<RelItem>();
                foreach (RelItem ri in Relatives)
                    if (
                        (string.IsNullOrEmpty(RelationType) || ri.RelationType == RelationType) &&
                        (string.IsNullOrEmpty(RelativeItemType) || ri.Relative.ItemType == RelativeItemType)
                       )
                        lst.Add(ri);
                return lst;
            }
        }

        /// <summary>
        /// Clones this item, without cloning its children.
        /// </summary>
        /// <param name="includeContent">If true the content is cloned as well.</param>
        /// <returns>The clone of this item.</returns>
        public virtual Item ShallowClone(bool includeContent)
        {
            Item result = ShallowCloneInternal(includeContent);
            EventSink.Instance.FireItemShallowCloned(new ItemEventArgs(result));
            return result;
        }

        private Item ShallowCloneInternal(bool includeContent)
        {
            Item result = Item.Create(this.name, this.itemType);
            // fields
            result.createdAt = this.createdAt;

            // null parent to avoid messing with order etc
            result.parent = null;

            // tags is easy
            foreach (string tag in this.tags)
                result.tags.Add(tag);

            // content now
            if (includeContent)
                CloneContent(this, result);

            return result;
        }

        private void CloneContent(Item source, Item dest)
        {
            if (source.content != null)
            {
                ContentBase cbNew = (ContentBase)source.content.Clone();
                dest.content = cbNew;
                dest.content.Container = dest;
                cbNew.AfterClone(source.content);
            }
        }

        private Item DeepCloneNoContent()
        {
            Item result = ShallowCloneInternal(false);
            try
            {
                result.TypedTreeValidationDisabled = true;

                foreach (Item child in this.internalChildren)
                {
                    Item copiedChild = child.DeepCloneNoContent();
                    result.Children.Add(copiedChild);
                }
            }
            finally
            {
                result.TypedTreeValidationDisabled = false;
            }
            return result;
        }

        private void CloneContentTree(Item source, Item dest)
        {
            CloneContent(source, dest);
            for (int i = 0; i < source.internalChildren.Count; i++)
                CloneContentTree(source.internalChildren[i], dest.internalChildren[i]);
        }

        /// <summary>
        /// Clones this item and all of its children.
        /// </summary>
        /// <param name="includeContent">If true, clones the content of the items as well.</param>
        /// <returns>The clone of this item.</returns>
        public virtual Item DeepClone(bool includeContent)
        {
            Item result = DeepCloneNoContent();
            if (includeContent)
                CloneContentTree(this, result);
            EventSink.Instance.FireItemDeepCloned(new ItemEventArgs(result));
            return result;
        }

        /// <summary>
        /// Clones this item and all of its children, adding the cloned item to the specified target parent.
        /// </summary>
        /// <param name="includeContent">If true, clones the content of the items as well.</param>
        /// <param name="targetParent">The target parent.</param>
        /// <returns>The clone of this item.</returns>
        public virtual Item DeepClone(bool includeContent, Item targetParent)
        {
            MyLog.Debug("DeepClone(includeContent,targetParent)");
            Item result = DeepCloneNoContent();
            if (targetParent != null)
                targetParent.Children.Add(result);
            if (includeContent)
                CloneContentTree(this, result);
            MyLog.Debug("DeepClone firing item deep cloned");
            EventSink.Instance.FireItemDeepCloned(new ItemEventArgs(result));
            MyLog.Debug("DeepClone fired item deep cloned");
            return result;
        }

        /// <summary>
        /// Checks if this item is ancestor of the given item.
        /// </summary>
        /// <param name="testChild">The item that is checked for being an ancestor.</param>
        /// <returns>True, if this item is ancestor of the given item.</returns>
        public virtual bool IsParentOf(Item testChild)
        {
            if (testChild == null)
                return false;

            for (Item it = testChild.Parent; it != null; it = it.Parent)
                if (it == this)
                    return true;
            return false;
        }

        /// <summary>
        /// Returns the higher ancestor of this item, i.e. the ancestor that has no parent.
        /// </summary>
        /// <returns>The ancestor of this item that has no parent.</returns>
        public virtual Item GetRootAncestor()
        {
            Item result = this;
            while (result.Parent != null)
                result = result.Parent;
            return result;
        }

        #endregion

        #region Typed Tree Validation

        private bool typedTreeValidationDisabled;

        /// <summary>
        /// This flag is used in deep clone for optimization so that the typedTreeValidation is not used.
        /// </summary>
        internal virtual bool TypedTreeValidationDisabled
        {
            get { return typedTreeValidationDisabled; }
            set { typedTreeValidationDisabled = value; }
        }

        /// <summary>
        /// Checks if the given item can be attached to this item, using a typed tree validator.
        /// If there's no typed tree validator, the operation is allowed.
        /// </summary>
        /// <param name="CandidateChild">The item that is going to be attached to this item.</param>
        /// <returns>True, if the given item can be attached to this item.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists that prevents <paramref name="CandidateChild"/> to be a attached to this item.
        /// </exception>
        public virtual bool CanAdd(Item CandidateChild)
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            return ttvalidator != null ? ttvalidator.CanAttach(this, CandidateChild) : true;
        }

        /// <summary>
        /// Checks if this item's parent can be changed to the given item, using a typed tree validator.
        /// If there's no typed tree validator, the operation is allowed.
        /// </summary>
        /// <param name="CandidateParent">The item that is going to be the new parent of this item.</param>
        /// <returns>True, if this item's parent can be changed to the given item.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists that prevents <paramref name="CandidateParent"/> to be the new parent of this item.
        /// </exception>
        public virtual bool CanChangeParent(Item CandidateParent)
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            return ttvalidator != null ? ttvalidator.CanAttach(CandidateParent, this) : true;
        }

        /// <summary>
        /// Using typed tree validation, returns an array of valid item types for this item's children.
        /// If typed tree validator doesn't exist, an empty array is returned.
        /// </summary>
        /// <returns>A string array of item types.</returns>
        public virtual string[] GetValidChildItemTypes()
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            if (ttvalidator != null)
            {
                List<string> types = new List<string>(ttvalidator.GetValidChildItemTypes(this));
                return types.ToArray();
            }
            else
                return new string[0];
        }

        /// <summary>
        /// Using typed tree validation, checks if the given content can be set as this item's content.
        /// If no typed tree validator exists, the operation is allowed.
        /// </summary>
        /// <param name="CandidateContent">The new item's content.</param>
        /// <returns>True, if the content can be set as this item's content.</returns>
        /// <exception cref="ValidationException">
        /// If a typed tree validator exists that prevents <paramref name="CandidateContent"/> to be set as the content of this item.
        /// </exception>
        public virtual bool CanSetContent(ContentBase CandidateContent)
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            return ttvalidator != null ? ttvalidator.CanContain(this, CandidateContent) : true;
        }

        /// <summary>
        /// Using typed tree validation, returns a list of content types (fully qualified type names) that can be
        /// set as this item's content. If no typed tree validator exists, an empty list is returned.
        /// </summary>
        /// <returns>A list of content types that can be set as this item's content.</returns>
        public virtual ICollection<string> GetValidContentTypes()
        {
            ITypedTreeValidator ttvalidator = NCommetContainer.Instance.TypedTreeValidator;
            return ttvalidator != null ? ttvalidator.GetValidContentTypes(this) : new List<string>();
        }

        #endregion

    }
}
